/*
 * Copyright 1999-2018 Alibaba Group Holding Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.xzzz.expand.sentinel.dubbo;

import com.alibaba.csp.sentinel.*;
import com.alibaba.csp.sentinel.context.ContextUtil;
import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.xzzz.common.base.pojo.R;
import com.xzzz.expand.sentinel.dubbo.config.DubboAdapterGlobalConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.Result;
import org.apache.dubbo.rpc.RpcException;

import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER;

/**
 * <p>Apache Dubbo service provider filter that enables integration with Sentinel. Auto activated by default.</p>
 * <p>Note: this only works for Apache Dubbo 2.7.x or above version.</p>
 * <p>
 * If you want to disable the provider filter, you can configure:
 * <pre>
 * &lt;dubbo:provider filter="-sentinel.dubbo.provider.filter"/&gt;
 * </pre>
 *
 * 支持与Sentinel集成的Apache Dubbo服务提供商过滤器。 默认自动激活。
 * 注意:这只适用于Apache Dubbo 2.7。 X或以上版本。
 * 如果你想禁用提供程序过滤器，你可以配置:
 * &lt;dubbo:provider filter="-sentinel.dubbo.provider.filter"/&gt;
 *
 * @author Carpenter Lee
 * @author Eric Zhao
 */
@Slf4j
@Activate(group = PROVIDER)
public class SentinelDubboProviderFilter extends BaseSentinelDubboFilter {

    public final static String TOTAL_IN_RESOURCE_NAME_CONSUMER = "__total_inbound_traffic__:";

    public SentinelDubboProviderFilter() {
        RecordLog.info("Sentinel Apache Dubbo provider filter initialized");
        log.info("[SENTINEL] 已适配 DUBBO BY DUBBO_FILTER");
    }

    @Override
    String getMethodName(Invoker invoker, Invocation invocation, String prefix) {
        return DubboUtils.getMethodResourceName(invoker, invocation, prefix);
    }

    @Override
    String getInterfaceName(Invoker invoker, String prefix) {
        return DubboUtils.getInterfaceName(invoker, prefix);
    }

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        // Get origin caller.
        String origin = DubboAdapterGlobalConfig.getOriginParser().parse(invoker, invocation);
        if (null == origin) {
            origin = "";
        }
        Entry invokerEntry = null;
        Entry methodEntry = null;
        String consumerName = invocation.getAttachment("remote.application");
        String prefix = DubboAdapterGlobalConfig.getDubboProviderResNamePrefixKey();
        String methodResourceName = getMethodName(invoker, invocation, prefix);
        try {

            // Only need to create entrance context at provider side, as context will take effect
            // at entrance of invocation chain only (for inbound traffic).
            // 只需要在提供者端创建入口上下文，因为上下文只在调用链的入口起作用(对于入站流量)。
            ContextUtil.enter("dubbo", origin);

            // 调用方的总流控
            invokerEntry = SphU.entry(TOTAL_IN_RESOURCE_NAME_CONSUMER + consumerName, ResourceTypeConstants.COMMON_RPC, EntryType.IN, invocation.getArguments());

            // 方法名流控, com.xzzz.usercenter.api.auth.TokenDubboService:login(com.xzzz.usercenter.api.auth.dto.DubboLoginDTO)
            methodEntry = SphU.entry(methodResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.IN, invocation.getArguments());

            Result result = invoker.invoke(invocation);

            /*
             * 业务异常, 提前在切面 {@link com.xzzz.common.web.exception.DubboExceptionAspect} 中被拦截, 所以无法在此处
             * 通过 {@link Result#getException()} 处理了, 切面封装业务异常的方式其实并不正确, 所以只能通过手动创建一个异常来处理了
             */
            try {
                if (result.getValue() instanceof R) {
                    R r = (R) result.getValue();
                    // 无法获取到异常信息
                    if (!r.isOk()) {
                        Tracer.traceEntry(new Exception(r.getMsg()), methodEntry);
                    }
                }
            } catch (Exception e) {

            }

            if (result.hasException()) {
                Tracer.traceEntry(result.getException(), methodEntry);
            }
            return result;
        } catch (BlockException e) {
            // 阻塞异常, 此处不配置规则, 所以不会被阻塞
            return DubboAdapterGlobalConfig.getProviderFallback().handle(invoker, invocation, e);
        } catch (RpcException e) {
            Tracer.traceEntry(e, methodEntry);
            throw e;
        } finally {
            if (methodEntry != null) {
                methodEntry.exit(1, invocation.getArguments());
            }

            if (invokerEntry != null) {
                invokerEntry.exit(1, invocation.getArguments());
            }

            ContextUtil.exit();
        }
    }


}

